#include <iostream>
#include <python2.6/Python.h>
#include <chrono>
#include <cstring>
#include <stdlib.h>
#include "ParkMiller.h"
#include "LCG.h"
#include "MTwister.h"
#include "comopencl.h"

using namespace std;

bool const PM = true;
bool const LCG = true;
bool const MT = true;
const int GPU = 0;
const int CPU = 1;
const int ALL = 2;
const int CPU_GPU = CPU;
const int MIN = 0;
const int MAX = 25;
int num ;//how manay random numbers a workitem will produce.
size_t global_size[] = { 1, 1 };
size_t local_size[] = { 1, 1 };
unsigned int total_len;
unsigned int *result;
string kernelsourcefile = "program.cl";

void printDict(PyObject* obj) {
	if (!PyDict_Check(obj)){
		cout << "error dict !" << endl;
		return;
	}
		
	PyObject *k, *keys;
	keys = PyDict_Keys(obj);
	for (int i = 0; i < PyList_GET_SIZE(keys); i++) {
		k = PyList_GET_ITEM(keys, i);
		
		char* c_name = PyByteArray_AsString(k);
		cout << c_name << endl;
	}
}

int analysis(unsigned int *result, int length, string rngname,unsigned long timeused){
	PyObject *pModule, *pFunc, *temp, *rList,*argTuple,*rngName;
	PyObject * MINNUM, *MAXNUM,*TimeUsed;
	Py_Initialize();
	PyRun_SimpleString("import sys");
	PyRun_SimpleString("sys.path.append('./')");
	if (!Py_IsInitialized())
		return 1;
	//cout << "1" << endl;
	rList = PyList_New(0);
	argTuple = PyTuple_New(5);
	//cout << "2" << endl;
	for (int i = 0; i < length; i++){
		temp = PyLong_FromLong(result[i]);
		//cout << result[i] << endl;
		if (PyList_Append(rList, temp) == -1){
			cout << "insert value error!" << endl;
			return 1;
		}
		Py_DECREF(temp);
	}
	rngName = PyByteArray_FromStringAndSize(rngname.c_str(),rngname.size());
	MINNUM = PyLong_FromLong(MIN);
	MAXNUM = PyLong_FromLong(MAX);
	TimeUsed = PyLong_FromLong(timeused);
	cout << "list size :" << PyList_GET_SIZE(rList) << endl;
	if (PyTuple_SetItem(argTuple, 0, rList) != 0){
		cout << "inset value 0 to argument error !" << endl;
		return 1;
	}
	if (PyTuple_SetItem(argTuple, 1, MINNUM) != 0){
		cout << "inset value 1 to argument error !" << endl;
		return 1;
	}
	if (PyTuple_SetItem(argTuple, 2, MAXNUM) != 0){
		cout << "inset value 2 to argument error !" << endl;
		return 1;
	}

	if (PyTuple_SetItem(argTuple, 3, rngName) != 0){
		cout << "inset value 3 to argument error !" << endl;
		return 1;
	}
	if (PyTuple_SetItem(argTuple, 4, TimeUsed) != 0){
		cout << "inset value 4 to argument error !" << endl;
		return 1;
	}
	pModule = PyImport_ImportModule("DataAnalysis");
	if (pModule == NULL){
		cout << "get module error !" << endl;
		return 1;
	}
	pFunc = PyObject_GetAttrString(pModule, "createplot");
	if (pFunc == NULL){
		cout << "get function error !" << endl;
		return 1;
	}
	if (!PyCallable_Check(pFunc)){
		cout << "function cannot callable!" << endl;
		return 1;
	}

	PyObject_CallObject(pFunc, argTuple);
	Py_DECREF(MINNUM);
	Py_DECREF(MAXNUM);
	Py_DECREF(TimeUsed);
	Py_DECREF(rngName);
	Py_DECREF(rList);
	Py_DECREF(argTuple);
	Py_DECREF(pFunc);
	Py_DECREF(pModule);
	Py_Finalize();
	return 0; 
}


unsigned long long testPM(){
	cout << "PM" << endl;
	unsigned int a = 16807;
	unsigned int m = 2147483647;
	
	cl_int status;
	chrono::time_point<chrono::system_clock> now = chrono::system_clock::now();
	unsigned int seed = (unsigned int)chrono::time_point_cast<chrono::seconds>(now).time_since_epoch().count();
	memset(result,0,total_len*sizeof(unsigned int));
	ComOpenCL comopencl(CPU_GPU, kernelsourcefile, "PMrng");
	cl_mem result_cl = clCreateBuffer(comopencl.context, 
									CL_MEM_ALLOC_HOST_PTR,
									total_len*sizeof(unsigned int),
									NULL,
									&status);
	if (status != CL_SUCCESS)
	{
		printf("Error: Create Buffer.\
			   			   			   (result_cl Buffer : clCreateBuffer)\n");
		exit(EXIT_FAILURE);
	}
	//chrono::time_point<chrono::system_clock> PMstart = chrono::system_clock::now();
	comopencl.setKernelArg(0, sizeof(unsigned int), (void*)&seed);
	comopencl.setKernelArg(1, sizeof(unsigned int), (void *)&a);
	comopencl.setKernelArg(2, sizeof(unsigned int), (void *)&m);
	comopencl.setKernelArg(3, sizeof(unsigned int), (void*)&num);
	comopencl.setKernelArg(4, sizeof(cl_mem), (void*)&result_cl);

	cl_event prof_event;

	comopencl.execTask(2,global_size,local_size,&prof_event);

	cl_ulong start_time = 0;
	cl_ulong end_time = 0;

	status = clGetEventProfilingInfo(prof_event,
		CL_PROFILING_COMMAND_START,
		sizeof(cl_ulong),
		&start_time,
		NULL);
	status = clGetEventProfilingInfo(prof_event,
		CL_PROFILING_COMMAND_END,
		sizeof(cl_ulong),
		&end_time,
		NULL);
	unsigned long timeused = (unsigned long)(end_time - start_time);
	cout << "Executing time :" << timeused << "ns" << endl;

	//chrono::time_point<chrono::system_clock> PMend = chrono::system_clock::now();

	//unsigned long long usedspan = (unsigned long long)chrono::duration_cast<chrono::microseconds>(PMend - PMstart).count();
	//cout << "PM RNG used " << ((float)usedspan) / 1000 << " ms." << "(" << usedspan << " mms)" << endl;
	comopencl.collectResult(result_cl, total_len*sizeof(unsigned int),result);

	cout << "begin to analysis result..." << endl;
	if (analysis(result, total_len,"PM",timeused) == 0){
		cout << "analysis successful !" << endl;
	}
	else{
		cout << "call analysis function error !" << endl;
		exit(EXIT_FAILURE);
	}
	return end_time - start_time;
}

unsigned long long testMT(){
	cout << "MT" << endl;
	cl_int status;
	chrono::time_point<chrono::system_clock> now = chrono::system_clock::now();
	unsigned int seed = (unsigned int)chrono::time_point_cast<chrono::seconds>(now).time_since_epoch().count();
	memset(result, 0, total_len*sizeof(unsigned int));
	ComOpenCL comopencl(CPU_GPU, kernelsourcefile, "MTrng");
	cl_mem result_cl = clCreateBuffer(comopencl.context,
		CL_MEM_ALLOC_HOST_PTR,
		total_len*sizeof(unsigned int),
		NULL,
		&status);
	if (status != CL_SUCCESS)
	{
		printf("Error: Create Buffer.\
			   			   			   			   (result_cl Buffer : clCreateBuffer)\n");
		exit(EXIT_FAILURE);
	}
	//chrono::time_point<chrono::system_clock> MTstart = chrono::system_clock::now();
	comopencl.setKernelArg(0, sizeof(unsigned int), (void*)&seed);
	comopencl.setKernelArg(1, sizeof(unsigned int), (void*)&num);
	comopencl.setKernelArg(2, sizeof(cl_mem), (void*)&result_cl);
	cl_event prof_event;
	comopencl.execTask(2, global_size, local_size,&prof_event);

	cl_ulong start_time = 0;
	cl_ulong end_time = 0;

	status = clGetEventProfilingInfo(prof_event,
		CL_PROFILING_COMMAND_START,
		sizeof(cl_ulong),
		&start_time,
		NULL);
	status = clGetEventProfilingInfo(prof_event,
		CL_PROFILING_COMMAND_END,
		sizeof(cl_ulong),
		&end_time,
		NULL);
	unsigned long timeused = (unsigned long)(end_time - start_time);
	cout << "Executing time :" << timeused<< "ns" << endl;
	//chrono::time_point<chrono::system_clock> MTend = chrono::system_clock::now();
	//unsigned long long usedspan = (unsigned long long)chrono::duration_cast<chrono::microseconds>(MTend - MTstart).count();
	//cout << "MT RNG used " << ((float)usedspan) / 1000 << " ms." << "(" << usedspan << " mms)" << endl;
	comopencl.collectResult(result_cl, total_len*sizeof(unsigned int), result);
	cout << "begin to analysis result..." << endl;
	if (analysis(result, total_len,"MT",timeused) == 0){
		cout << "analysis successful !" << endl;
	}
	else{
		cout << "call analysis function error !" << endl;
		exit(EXIT_FAILURE);
	}
	return end_time - start_time;
}

unsigned long long testLCG(){
	cout << "LCG" << endl;
	unsigned int a = 1103515245;
	unsigned int c = 12345;
	unsigned int m = 0x80000000;
	cl_int status;
	chrono::time_point<chrono::system_clock> now = chrono::system_clock::now();
	unsigned int seed = (unsigned int)chrono::time_point_cast<chrono::seconds>(now).time_since_epoch().count();
	memset(result, 0, total_len*sizeof(unsigned int));
	ComOpenCL comopencl(CPU_GPU, kernelsourcefile, "LCGrng");
	cl_mem result_cl = clCreateBuffer(comopencl.context,
		CL_MEM_ALLOC_HOST_PTR,
		total_len*sizeof(unsigned int),
		NULL,
		&status);
	if (status != CL_SUCCESS)
	{
		printf("Error: Create Buffer.\
			   			   			   			   (result_cl Buffer : clCreateBuffer)\n");
		exit(EXIT_FAILURE);
	}
	chrono::time_point<chrono::system_clock> LCGstart = chrono::system_clock::now();
	comopencl.setKernelArg(0, sizeof(unsigned int), (void*)&seed);
	comopencl.setKernelArg(1, sizeof(unsigned int), (void *)&a);
	comopencl.setKernelArg(2, sizeof(unsigned int), (void *)&c);
	comopencl.setKernelArg(3, sizeof(unsigned int), (void *)&m);
	comopencl.setKernelArg(4, sizeof(unsigned int), (void*)&num);
	comopencl.setKernelArg(5, sizeof(cl_mem), (void*)&result_cl);
	cl_event prof_event;
	comopencl.execTask(2, global_size, local_size,&prof_event);
	cl_ulong start_time = 0;
	cl_ulong end_time = 0;

	status = clGetEventProfilingInfo(prof_event,
		CL_PROFILING_COMMAND_START,
		sizeof(cl_ulong),
		&start_time,
		NULL);
	status = clGetEventProfilingInfo(prof_event,
		CL_PROFILING_COMMAND_END,
		sizeof(cl_ulong),
		&end_time,
		NULL);
	unsigned long timeused = (unsigned long)(end_time - start_time);
	cout << "Executing time :" << timeused<< "ns" << endl;
	//chrono::time_point<chrono::system_clock> LCGend = chrono::system_clock::now();
	//unsigned long long usedspan = (unsigned long long)chrono::duration_cast<chrono::microseconds>(LCGend - LCGstart).count();
	//cout << "LCG RNG used " << ((float)usedspan) / 1000 << " ms." << "(" << usedspan << " mms)" << endl;
	comopencl.collectResult(result_cl, total_len*sizeof(unsigned int), result);
	cout << "begin to analysis result..." << endl;
	if (analysis(result, total_len,"LCG",timeused) == 0){
		cout << "analysis successful !" << endl;
	}
	else{
		cout << "call analysis function error !" << endl;
		exit(EXIT_FAILURE);
	}
	return end_time - start_time;
}

unsigned long long testXS(){
	cout << "XS" << endl;
	cl_int status;
	chrono::time_point<chrono::system_clock> now = chrono::system_clock::now();
	unsigned int seed = (unsigned int)chrono::time_point_cast<chrono::seconds>(now).time_since_epoch().count();
	memset(result, 0, total_len*sizeof(unsigned int));
	ComOpenCL comopencl(CPU_GPU, kernelsourcefile, "XSrng");
	cl_mem result_cl = clCreateBuffer(comopencl.context,
		CL_MEM_ALLOC_HOST_PTR,
		total_len*sizeof(unsigned int),
		NULL,
		&status);
	if (status != CL_SUCCESS)
	{
		printf("Error: Create Buffer.\
			   			   			   			   			   (result_cl Buffer : clCreateBuffer)\n");
		exit(EXIT_FAILURE);
	}
	//chrono::time_point<chrono::system_clock> XSstart = chrono::system_clock::now();
	comopencl.setKernelArg(0, sizeof(unsigned int), (void*)&seed);
	comopencl.setKernelArg(1, sizeof(unsigned int), (void*)&num);
	comopencl.setKernelArg(2, sizeof(cl_mem), (void*)&result_cl);
	cl_event prof_event;

	comopencl.execTask(2, global_size, local_size,&prof_event);
	cl_ulong start_time = 0;
	cl_ulong end_time = 0;

	status = clGetEventProfilingInfo(prof_event,
		CL_PROFILING_COMMAND_START,
		sizeof(cl_ulong),
		&start_time,
		NULL);
	status = clGetEventProfilingInfo(prof_event,
		CL_PROFILING_COMMAND_END,
		sizeof(cl_ulong),
		&end_time,
		NULL);
	unsigned long timeused = (unsigned long)(end_time - start_time);
	cout << "Executing time :" << timeused<< "ns" << endl;
	//chrono::time_point<chrono::system_clock> XSend = chrono::system_clock::now();
	//unsigned long long usedspan = (unsigned long long)chrono::duration_cast<chrono::microseconds>(XSend - XSstart).count();
	//cout << "MT RNG used " << ((float)usedspan) / 1000 << " ms." <<"("<<usedspan<<" mms)"<< endl;
	comopencl.collectResult(result_cl, total_len*sizeof(unsigned int), result);
	cout << "begin to analysis result..." << endl;
	if (analysis(result, total_len, "XS",timeused) == 0){
		cout << "analysis successful !" << endl;
	}
	else{
		cout << "call analysis function error !" << endl;
		exit(EXIT_FAILURE);
	}
	return end_time - start_time;
}

int main(int argc, char *argv[]){
	if (argc < 3){
		cout << "Usage:testRNGs <RNG-name> <RN number>" << endl;
		return 1;
	}
	else{
		num = atoi(argv[argc-1]);
		if (num == 0){
			cout << "parameter error !" << endl;
			return 1;
		}
		total_len = num;
		result = (unsigned int *)calloc(total_len,sizeof(unsigned int));
		if (NULL == result){
			cout << "calloc error !" << endl;
			return 1;
		}

#if defined(_MSC_VER)
			//cout << "VC" << endl;
			for (int i = 1; i < argc-1; i++){
				//cout << argv[i] << " " << endl;
				
				if (_strcmpi(argv[i], "PM") == 0){
					testPM();
				}
				else if (_strcmpi(argv[i], "MT") == 0){
					testMT();
				}
				else if (_strcmpi(argv[i], "LCG") == 0){
					testLCG();
				}
				else if (_strcmpi(argv[i], "XS") == 0){
					testXS();
				}
				else{
					cout << "unrecognized parameter: " << argv[i] << endl;
				}
			}

#elif defined(__GNUC__)
			//cout << "GCC"<<endl;
			for (int i = 1; i < argc-1; i++){
				//cout << argv[i] << " " << endl;
				if (strcmp(argv[i], "PM") == 0){
					testPM();
				}
				else if (strcmp(argv[i], "MT") == 0){
					testMT();
				}
				else if (strcmp(argv[i], "LCG") == 0){
					testLCG();
				}
				else if (strcmp(argv[i], "XS") == 0){
					testXS();
				}
				else{
					cout << "unrecognized parameter: " << argv[i] << endl;
				}
			}

#else
			for (int i = 1; i < argc-1; i++){
				if (strcmpi(argv[i], "PM") == 0){
					testPM();
				}
				else if (strcmpi(argv[i], "MT") == 0){
					testMT();
				}
				else if (strcmpi(argv[i], "LCG") == 0){
					testLCG();
				}
				else if (strcmpi(argv[i], "XS") == 0){
					testXS();
				}
				else{
					cout << "unrecognized parameter: " << argv[i] << endl;
				}
			}	
#endif

			delete result;
		cout << "OK !" << endl;
		return 0;
	}
}



























/*
int main1(int argc, char *argv[]){
	int num = 10000;
	int resultPM[101];
	int resultLCG[101];
	int resultMT[101];
	int total = 0;
	memset(resultPM, 0, 101 * sizeof(int));
	memset(resultLCG, 0, 101 * sizeof(int));
	memset(resultMT, 0, 101 * sizeof(int));
	chrono::time_point<chrono::system_clock> now = chrono::system_clock::now();

	//cout << "current time :" << chrono::time_point_cast<chrono::milliseconds>(today).time_since_epoch().count()<< endl;
	PMrng pmrng((int)chrono::time_point_cast<chrono::seconds>(now).time_since_epoch().count());
	LCGrng lcgrng((int)chrono::time_point_cast<chrono::seconds>(now).time_since_epoch().count());
	MTrng mtrng((int)chrono::time_point_cast<chrono::seconds>(now).time_since_epoch().count());
	for (int i = 0; i < num; i++){
		//cout << i << ":" <<pmrng.getNext(1,10)<< endl;
		
		if (PM){
			resultPM[pmrng.getNext(1, 100)]++;
		}
			
		if (LCG){
			resultLCG[lcgrng.getNext(1, 100)]++;
		}
		if (MT){
			resultMT[mtrng.getNext(1, 100)]++;
		}
	}
	if (PM){
		total = 0;
		cout << "PM:" << endl;
		for (int i = 0; i < 101; i++){
			cout << i << ":" << resultPM[i] << endl;
			total += resultPM[i];
		}
		cout << "Total:" << total << endl;
	}
	if (LCG){
		total = 0;
		cout << "LCG:" << endl;
		for (int i = 0; i < 101; i++){
			cout << i << ":" << resultLCG[i] << endl;
			total += resultLCG[i];
		}
		cout << "Total:" << total << endl;
	}
	if (MT){
		total = 0;
		cout << "MT:" << endl;
		for (int i = 0; i < 101; i++){
			cout << i << ":" << resultMT[i] << endl;
			total += resultMT[i];
		}
		cout << "Total:" << total << endl;
	}
	
}
*/
